import os
import imp
import abc
import logging
import ConfigParser

from pluginlib import PluginError

logger = logging.getLogger('pluginglib.plugin')

class PluginLoadingError(PluginError):
    pass

class PluginNotLoadedError(PluginError):
    pass

class PluginAlreadyActiveError(PluginError):
    pass

class Plugin(object):
    """A plugin."""

    @classmethod
    def create(cls, mgr, plugin_type, path):
        if plugin_type == 'dir':
            return DirectoryPlugin(mgr, path)
        elif plugin_type == 'zip':
            return ZippedPlugin(mgr, path)

    @property
    def active(self):
        return (self._ctx is not None) and self._ctx.active

    def __init__(self, mgr, path):
        self.mgr = mgr
        self.path = path

        self.name = None
        self.version = None
        self.load_modules = None

        self.loaded = False

        self._ctx = None

    def load(self):
        """Load the plugin from the module."""

        if self.loaded:
            raise PluginLoadingError("Plugin already loaded")

        logger.info("Loading plugin from %s" % self.path)

        cfg = self._load_config()
        self.name = cfg.get('Plugin', 'name')
        if '.' in self.name:
            raise PluginLoadingError("Plugin names can't have full stops.")
        self.version = parse_version(cfg.get('Plugin', 'version'))
        self.load_modules = cfg.get('Plugin', 'load modules').splitlines()

        self._ctx = self.mgr.context.new_plugin_context()

        try:
            for module_name in self.load_modules:
                self._load_module(module_name)
        except:
            logger.exception("Error loading plugin %s" % self.name)
            self.mgr.context.remove_plugin_context(self._ctx)
            self._ctx = None
            raise PluginLoadingError(self.name)

        self.loaded = True

    def _load_module(self, module_name):
        """Load a module from the plugin and extract ifaces and impls."""

        if module_name == '.':
            module_name = '__init__'
        imported_name = 'pluginlib.pluginmodules.%s.%s' % (self.name,
                                                           module_name)
        desc = imp.find_module(module_name, self._get_import_path())
        module = imp.load_module(imported_name, *desc)

        if hasattr(module, '__all__'):
            module_objects_names = module.__all__
        else:
            module_objects_names = dir(module)

        for object_name in module_objects_names:
            obj = getattr(module, object_name)
            if isinstance(obj, type):
                if issubclass(obj, Interface):
                    self._ctx.add_interface(obj)
                elif issubclass(obj, Implementation):
                    self._ctx.add_implementation(obj)

    def activate(self):
        if not self.loaded:
            raise PluginNotLoadedError(self)
        if self._ctx.active:
            raise PluginAlreadyActiveError(self)

        self._ctx.activate()

    def deactivate(self):
        if self._ctx is not None:
            self._ctx.deactivate()

    def _load_config(self):
        """Create a ConfigParser with the configuration of the plugin."""
        raise NotImplemented()

    def _get_import_path(self):
        """Return the path for importing modules from the plugin."""
        raise NotImplemented()

class DirectoryPlugin(Plugin):
    """A plugin that loads from a directory.
    """

    def _load_config(self):
        cfg = ConfigParser.ConfigParser()
        cfg.read([os.path.join(self.path, 'plugin.conf')])
        return cfg

    def _get_import_path(self):
        return [self.path]

class ZippedPlugin(Plugin):
    """A plugin that loads from a zip file.
    """

class PluginComponent(abc.ABCMeta):

    # Override the ABCMeta cache so that it always uses __subclasshook__
    def get_abc_cache(self):
        return set()
    def set_abc_cache(self, value):
        pass
    _abc_cache = property(get_abc_cache, set_abc_cache)
    def get_abc_negative_cache(self):
        return set()
    def set_abc_negative_cache(self, value):
        pass
    _abc_negative_cache = property(get_abc_negative_cache, set_abc_negative_cache)

    def __init__(self, name, bases, attributes):
        abc.ABCMeta.__init__(self, name, bases, attributes)

        self.name = name
        self.active = False

    def activate(self):
        self.active = True

    def deactivate(self):
        self.active = False

def parse_version(version_string):
    return version_string

class Interface(object):
    """An interface."""

    __metaclass__ = PluginComponent

    @classmethod
    def __subclasshook__(cls, other):
        return (issubclass(other, Implementation) and
                other.iface_name == cls.name and
                other.active)

#     def __init__(self, name, display_name):
#         self.name = name
#         self.display_name = display_name

class Implementation(object):
    """An inplementation of an interface."""

    __metaclass__ = PluginComponent

#     def __init__(self, iface_name, display_name):
#         self.iface_name = iface_name
#         self.name = name
#         self.display_name = display_name

